#include "udatetimerangeedit.h"
#include "ucalendarwidget.h"
#include "ulabel.h"

#include <QHBoxLayout>
#include <QPainter>
#include <QDebug>
#include <QAction>
#include <QDateTime>
#include <QStyleOption>
#include <QApplication>
#include <QMouseEvent>
#include <QPainterPath>
#include <QScreen>
#include <QTimer>

namespace
{
    const QString format = "yyyy-MM-dd hh:mm:ss";
}

class UDatetimeRangeEditPrivate
{
public:
    UDatetimeRangeEditPrivate(UDatetimeRangeEdit* q) : q_ptr(q) {}
    ~UDatetimeRangeEditPrivate() = default;

    void init();

    void initConnection();

    QString convertDateTime(const QDateTime &time) const;

    QLine getLinePosition(UDatetimeRangeEdit::LinePosition linePosition) const;

    QRect widgetMapToGlobal(const QRect &rect) const;

    QPoint updateCalendarPos(UDatetimeRangeEdit::LinePosition linePosition);

private:
    Q_DECLARE_PUBLIC(UDatetimeRangeEdit)
    Q_DISABLE_COPY(UDatetimeRangeEditPrivate)
    UDatetimeRangeEdit* const q_ptr = nullptr;

    ULabel* startTimeLabel = nullptr;
    ULabel* endTimeLabel = nullptr;
    UCalendarWidget* calendarWidget = nullptr;
    ULabel* iconLabel = nullptr;
    UDatetimeRangeEdit::LinePosition position = UDatetimeRangeEdit::None;
    QHBoxLayout* layout = nullptr;
    QTimer timer;
    bool isClicked = false;
    double borderWidth = 1.5;
    int radius = 8;
};

void UDatetimeRangeEditPrivate::init()
{
    Q_Q(UDatetimeRangeEdit);

    layout = new QHBoxLayout;
    layout->setContentsMargins(10, 2, 10, 2);

    iconLabel = new ULabel(q);
    iconLabel->setFixedSize(18, 18);
    iconLabel->setIcon(QIcon::fromTheme("utk_calendar"));

    startTimeLabel = new ULabel(q);
    startTimeLabel->setText(convertDateTime(QDateTime::currentDateTime()));
    startTimeLabel->installEventFilter(q);

    endTimeLabel = new ULabel(q);
    endTimeLabel->setText(convertDateTime(QDateTime::currentDateTime()));
    endTimeLabel->installEventFilter(q);

    auto widget = q->parentWidget() ? q->parentWidget() : q;
    calendarWidget = new UCalendarWidget(widget);
    calendarWidget->setWindowFlags(Qt::WindowType::Popup);
    calendarWidget->hide();

    QLabel* label = new QLabel("-", q);

    layout->addWidget(iconLabel);
    layout->addWidget(startTimeLabel);
    layout->addWidget(label);
    layout->addWidget(endTimeLabel);
    q->setLayout(layout);

    initConnection();
    timer.setInterval(100);
    qApp->installEventFilter(q);
    q->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
}

void UDatetimeRangeEditPrivate::initConnection()
{
    Q_Q(UDatetimeRangeEdit);

    q->connect(calendarWidget, &UCalendarWidget::closed, q, [ = ]()
    {
        q->setLinePosition(UDatetimeRangeEdit::None);
        isClicked = false;
    });

    q->connect(calendarWidget, &UCalendarWidget::selectDateTime, q, &UDatetimeRangeEdit::onSelectDateTime);
}

QString UDatetimeRangeEditPrivate::convertDateTime(const QDateTime &time) const
{
    return time.toString(format);
}

QLine UDatetimeRangeEditPrivate::getLinePosition(UDatetimeRangeEdit::LinePosition linePosition) const
{
    QLine line = QLine();
    switch (linePosition)
    {
        case UDatetimeRangeEdit::Left:
        {
            QPoint startPoint(startTimeLabel->geometry().bottomLeft());
            startPoint.setY(startPoint.y() + 2);
            QPoint endPoint(startTimeLabel->geometry().bottomRight());
            endPoint.setY(endPoint.y() + 2);
            line.setP1(startPoint);
            line.setP2(endPoint);
            break;
        }
        case UDatetimeRangeEdit::Right:
        {
            QPoint startPoint(endTimeLabel->geometry().bottomLeft());
            startPoint.setY(startPoint.y() + 2);
            QPoint endPoint(endTimeLabel->geometry().bottomRight());
            endPoint.setY(endPoint.y() + 2);
            line.setP1(startPoint);
            line.setP2(endPoint);
            break;
        }
        default:
            break;
    }

    return line;
}

QRect UDatetimeRangeEditPrivate::widgetMapToGlobal(const QRect &rect) const
{
    Q_Q(const UDatetimeRangeEdit);
    QPoint point = q->mapToGlobal(rect.topLeft());
    return QRect(point, QSize(rect.width(), rect.height()));
}

QPoint UDatetimeRangeEditPrivate::updateCalendarPos(UDatetimeRangeEdit::LinePosition linePosition)
{
    Q_Q(UDatetimeRangeEdit);

    QPoint pos = QPoint(q->rect().bottomLeft().x(), q->rect().bottomLeft().y() + 2);
    (UDatetimeRangeEdit::Left == linePosition) ? pos.setX(startTimeLabel->geometry().x()) : pos.setX(endTimeLabel->geometry().x());
    QPoint pos2 = QPoint(q->rect().topLeft().x(), q->rect().topLeft().y() - 2);
    (UDatetimeRangeEdit::Left == linePosition) ? pos2.setX(startTimeLabel->geometry().x()) : pos2.setX(endTimeLabel->geometry().x());
    pos = q->mapToGlobal(pos);
    pos2 = q->mapToGlobal(pos2);
    QSize size = calendarWidget->rect().size();
    QRect screen = QGuiApplication::screenAt(pos)->availableGeometry();

    if (pos.x() + size.width() > screen.right())
        pos.setX(screen.right() - size.width());

    pos.setX(qMax(pos.x(), screen.left()));

    if (pos.y() + size.height() > screen.bottom())
        pos.setY(pos2.y() - size.height());
    else if (pos.y() < screen.top())
        pos.setY(screen.top());

    if (pos.y() < screen.top())
        pos.setY(screen.top());

    if (pos.y() + size.height() > screen.bottom())
        pos.setY(screen.bottom() - size.height());

    return pos;
}

UDatetimeRangeEdit::UDatetimeRangeEdit(QWidget* parent) : QLineEdit(parent)
    , d_ptr(new UDatetimeRangeEditPrivate(this))
{
    Q_D(UDatetimeRangeEdit);

    d->init();
}

UDatetimeRangeEdit::~UDatetimeRangeEdit()
{
}

void UDatetimeRangeEdit::setLinePosition(UDatetimeRangeEdit::LinePosition linePosition)
{
    Q_D(UDatetimeRangeEdit);

    if (d->position != linePosition)
    {
        d->position = linePosition;
        update();
    }
}

void UDatetimeRangeEdit::showCalendarWidget(UDatetimeRangeEdit::LinePosition linePosition, const QPoint &point)
{
    Q_D(UDatetimeRangeEdit);

    if (!isVisible())
        return;

    d->isClicked = true;

    setLinePosition(linePosition);
    if (linePosition == LinePosition::Left)
    {
        QDateTime dateTime = startDateTime();
        d->calendarWidget->setDateTime(dateTime);
        d->calendarWidget->stopTime();
    }
    else if (linePosition == LinePosition::Right)
    {
        this->stopTime();
        QDateTime startTime = startDateTime();
        QDateTime endTime = endDateTime();
        if (endTime < startTime)
        {
            endTime = startTime;
            d->endTimeLabel->setText(d->convertDateTime(endTime));
        }

        d->calendarWidget->setDateTime(startTime);
        d->calendarWidget->startTime();
    }

    d->calendarWidget->move(point);
    d->calendarWidget->show();
}

void UDatetimeRangeEdit::setStartDateTime(const QDateTime &dateTime)
{
    Q_D(UDatetimeRangeEdit);
    d->startTimeLabel->setText(d->convertDateTime(dateTime));
}

void UDatetimeRangeEdit::setEndDateTime(const QDateTime &dateTime)
{
    Q_D(UDatetimeRangeEdit);
    d->endTimeLabel->setText(d->convertDateTime(dateTime));
}

QDateTime UDatetimeRangeEdit::startDateTime() const
{
    Q_D(const UDatetimeRangeEdit);
    return QDateTime::fromString(d->startTimeLabel->text(), format);
}

QDateTime UDatetimeRangeEdit::endDateTime() const
{
    Q_D(const UDatetimeRangeEdit);
    return QDateTime::fromString(d->endTimeLabel->text(), format);
}

void UDatetimeRangeEdit::startTime()
{
    Q_D(UDatetimeRangeEdit);
    if (!d->timer.isActive())
        d->timer.start();
}

void UDatetimeRangeEdit::stopTime()
{
    Q_D(UDatetimeRangeEdit);
    if (d->timer.isActive())
        d->timer.stop();
}

bool UDatetimeRangeEdit::isActived() const
{
    Q_D(const UDatetimeRangeEdit);
    return d->timer.isActive();
}

void UDatetimeRangeEdit::paintEvent(QPaintEvent* event)
{
    Q_D(UDatetimeRangeEdit);
    QLine line = d->getLinePosition(d->position);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    QStyleOption opt;
    opt.init(this);
    QPen pen = QPen(Qt::transparent, 1);

    if (opt.state & QStyle::State_Enabled)
    {
        if (opt.state & QStyle::State_Selected)
        {
            pen = QPen(palette().color(QPalette::Highlight), 1);
        }
        else if (opt.state & QStyle::State_MouseOver)
        {
            pen = QPen(palette().color(QPalette::Text), 1);
        }
    }

    if (d->isClicked)
        pen = QPen(palette().color(QPalette::Highlight), 1);

    QPainterPath rectPath;
    QRect rect = this->rect().adjusted(d->borderWidth, d->borderWidth, -d->borderWidth, -d->borderWidth);
    rectPath.addRoundedRect(rect, d->radius, d->radius);
    painter.setBrush(palette().color(QPalette::Button));
    painter.setPen(pen);
    painter.drawPath(rectPath);

    if (!line.isNull())
    {
        painter.setPen(QPen(palette().color(QPalette::Highlight), 2));
        painter.drawLine(line);
    }
}

bool UDatetimeRangeEdit::eventFilter(QObject* obj, QEvent* event)
{
    Q_D(UDatetimeRangeEdit);
    if ((parentWidget() && !parentWidget()->isVisible()) || !this->isVisible())
    {
        return QObject::eventFilter(obj, event);
    }

    if (event->type() == QEvent::MouseButtonPress)
    {
        auto w = QApplication::widgetAt(QCursor::pos());
        if (!isAncestorOf(w))
        {
            return false;
        }

        const QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event);
        if (d->widgetMapToGlobal(d->startTimeLabel->geometry()).contains(mouseEvent->globalPos()))
        {
            showCalendarWidget(LinePosition::Left, d->updateCalendarPos(LinePosition::Left));
            return true;
        }
        else if (d->widgetMapToGlobal(d->endTimeLabel->geometry()).contains(mouseEvent->globalPos()))
        {
            showCalendarWidget(LinePosition::Right, d->updateCalendarPos(LinePosition::Right));
            return true;
        }
    }

    return QObject::eventFilter(obj, event);
}

QSize UDatetimeRangeEdit::sizeHint() const
{
#ifdef Q_OS_MAC
    return QSize(310, 24);
#else
    return QSize(305, 24);
#endif
}

void UDatetimeRangeEdit::onSelectDateTime(const QDateTime &time)
{
    Q_D(UDatetimeRangeEdit);
    bool isStart = (LinePosition::Left == d->position);
    QDateTime dateTime = time;
    if (isStart)
    {
        if (dateTime > endDateTime())
            dateTime = endDateTime();
        setStartDateTime(dateTime);
        showCalendarWidget(LinePosition::Right, d->updateCalendarPos(LinePosition::Right));
    }
    else
    {
        if (dateTime < startDateTime())
            dateTime = startDateTime();
        setEndDateTime(dateTime);
        d->calendarWidget->stopTime();
        d->calendarWidget->close();
        emit dateTimeChanged(qMakePair(startDateTime(), endDateTime()));
    }
}
